# encoding: utf-8
""" 
@version: v1.0 
@author: autumner 
@license: Apache Licence  
@contact: 18322313385@163.com 
@site:  https://gitee.com/autumner/pythoncookbook
@software: PyCharm 
@file: xmlrpc_server.py 
@time: 2019/7/19 上午9:04
"""
'''
使用xml-rpc进行文件共享
1.编写服务端
'''
from xmlrpc.client import ServerProxy, Fault, Binary
from os import walk
from os.path import join, abspath, isfile, getsize, getatime
from xmlrpc.server import SimpleXMLRPCServer
from urllib.parse import urlparse
import sys
import time

# 允许地址重用
SimpleXMLRPCServer.allow_reuse_address = 1

MAX_HISTORY_LENGTH = 6

UNHANDLED = 100
ACCESS_DENIED = 200

class UnhandledQuery(Fault):
    """
    表示查询未得到处理的异常
    """
    def __init__(self, message="Couldn't handle the query"):
        super().__init__(UNHANDLED, message)

class AccessDenied(Fault):
    """
    用户试图访问未获得授权的资源时将引发的异常
    """
    def __init__(self, message="Access denied"):
        super().__init__(ACCESS_DENIED, message)


def inside(dir, name):
    """
    检查指定的目录是否包含指定的文件
    :param dir:
    :param name:
    :return:
    """
    dir = abspath(dir)
    name = abspath(name)
    return name.startswith(join(dir, ''))

def get_port(url):
    """
    从URL中提取端口号
    :param url:
    :return:
    """
    name = urlparse(url)[1]
    parts = name.split(':')
    return int(parts[-1])

def timeStampTotime(timestamp):
    """
    把时间戳转化为时间
    :param timestamp:
    :return:
    """
    timeStruct = time.localtime(timestamp)
    return time.strftime('%Y-%m-%d %H:%M:%S', timeStruct)

def formatSize(bytes):
    """
    字节bytes转换B\KB\M\G
    :param bytes:
    :return:
    """
    try:
        bytes = float(bytes)
    except:
        print("输入的字节格式不对")
        return "input bytes Error"
    i = 0
    units = ["B", "KB", "MB", "GB", "TB"]

    while (abs(bytes) >= 1024):
        bytes = bytes / 1024
        i += 1
        if (i == 4): break
    return f"{round(bytes,2)}{units[i]}"

def get_FileSize(filepath):
    """
    获取文件大小
    :param filepath:
    :return:
    """
    fsize = getsize(filepath)
    return formatSize(fsize)

def get_fAccessT(filepath):
    """
    获取文件访问时间
    :param filepath:
    :return:
    """
    t = getatime(filepath)
    return timeStampTotime(t)

class Node:
    """
    P2P网络中的节点
    """
    def __init__(self, url, dirname, secret):
        self.url = url
        self.dirname = dirname
        self.secret = secret
        self.known = set()

    def list(self, secret):
        """
        获取客户端共享文件列表
        :param secret:
        :return:
        """
        for root, dirs, files in walk(self.dirname):
            if files:
                for file in files:
                    print(f"{get_fAccessT(join(self.dirname,file)):20}{get_FileSize(join(self.dirname,file))}  {file:6}")
        return 0

    def query(self, query, history=[]):
        """
        查询文件（可能向已知节点寻求帮助），并以字符串的方式返回它
        :param query:
        :param history:
        :return:
        """
        try:
            return self._handle(query)
        except UnhandledQuery:
            history = history + [self.url]
            if len(history) >= MAX_HISTORY_LENGTH: raise
            return self._broadcast(query, history)

    def hello(self, other):
        """
        用于向其他节点介绍当前节点
        :param other:
        :return:
        """
        self.known.add(other)
        return 0

    def fetch(self, query, secret):
        """
        用于让节点查找并下载文件
        :param query:
        :param secret:
        :return:
        """
        if secret != self.secret: raise AccessDenied
        result = self.query(query)
        f = open(join(self.dirname, query), 'wb')
        # print("远程目录",self.dirname, result)
        f.write(result.data)
        f.close()
        return 0

    def _start(self):
        """
        供内部用来启动XML-RPC服务器
        :return:
        """
        s = SimpleXMLRPCServer(("", get_port(self.url)), logRequests=False)
        s.register_instance(self)
        s.serve_forever()

    def _handle(self, query):
        """
        供内部用来处理查询
        :param query:
        :return:
        """
        dir = self.dirname
        name = join(dir, query)
        if not isfile(name): raise UnhandledQuery
        if not inside(dir, name): raise AccessDenied
        return Binary(open(name,'rb').read())

    def _broadcast(self, query, history):
        """
        供内部用来向所有已知节点广播查询
        :param query:
        :param history:
        :return:
        """
        for other in self.known.copy():
            if other in history:continue
            try:
                s = ServerProxy(other)
                # print(s.query(query, history))
                return s.query(query, history)
            except Fault as f:
                if f.faultCode == UNHANDLED: pass
                else: self.known.remove(other)
            except:
                self.known.remove(other)
        raise UnhandledQuery


def main():
    url, directory, secret = sys.argv[1:]
    n = Node(url, directory, secret)
    n._start()

if __name__ == '__main__': main()

# Example useage
"""
Terminal:
python xmlrpc_server.py http://localhost:4342 sharedir secret

"""
